/*
 * Copyright Iain Sproat, 2009
 * 
 *
 */
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Reflection;

using log4net;

using FreeBase.Domain;

namespace FreeBase
{
	public interface IResponseParser
	{
		Response<T> Parse<T>( string response );
		ResponseForLinq<T> ParseForLinq<T>( string response );
	}
	/// <summary>
	/// Description of ResponseParser.
	/// </summary>
	public class ResponseParser : IResponseParser
	{
		private readonly ILog logger = LogManager.GetLogger( typeof( ResponseParser ) );
		public ResponseParser()
		{
		}
		
		public Response<T> Parse<T>( string response )
		{
			if( string.IsNullOrEmpty( response ) )
				throw new ArgumentNullException( "response" );
			logger.Debug( "Attempting to Parse type of " + typeof(T).Name );

			Response<T> deserialized = JsonConvert.DeserializeObject<Response<T>>(response);
			
			if( deserialized == null )
			{
				logger.Debug( "JSON Deserializer returned null for " + typeof(T).Name );
				throw new ApplicationException( "Could not correctly deserialize " + typeof(T).Name );
			}
			return deserialized;
		}
		
		
		public ResponseForLinq<T> ParseForLinq<T>( string response )
		{
			if( string.IsNullOrEmpty( response ) )
				throw new ArgumentNullException( "response" );
			
			logger.Debug( "Attempting to Parse type of " + typeof(T).Name );
			#if DEBUG
			if( typeof(T).IsGenericType )
			{
				Type[] genericTypes = typeof(T).GetGenericArguments();
				Type internalGenericType = genericTypes[0];
				logger.Debug( "internal generic type is " + internalGenericType.Name );
			}
			#endif

			ResponseForLinq<T> deserialized = JsonConvert.DeserializeObject<ResponseForLinq<T>>(response);
			if( deserialized == null )
			{
				logger.Debug( "JSON Deserializer returned null for " + typeof(T).Name );
				throw new ApplicationException( "Could not correctly deserialize " + typeof(T).Name );
			}
			return deserialized;
		}
	}
	
	public class ValueTypeConvertor<T> : JsonConverter
	{
		public override bool CanConvert(Type objectType)
		{
			return typeof(T).IsAssignableFrom(objectType);
		}
		
		public override object ReadJson(JsonReader reader, Type objectType)
		{
			switch( reader.TokenType ){
				case JsonToken.String :
					return Activator.CreateInstance(typeof(T), (string)reader.Value);
				case JsonToken.StartObject :
					JToken token;
					using (JsonTokenWriter writer = new JsonTokenWriter())
					{
						writer.WriteToken(reader);
						token = writer.Token;
					}
					return ConvertTokenToType( token, objectType );
				default:
					throw new NotImplementedException( "Token being converted is not a StartObject or a String" );
			}
		}
		
		public override void WriteJson(JsonWriter writer, object value)
		{
			//TODO
			throw new NotImplementedException();
		}
		
		private object ConvertTokenToType( JToken token, Type objectType )
		{
			if( token == null )
				throw new ArgumentNullException( "token" );
			if( objectType == null )
				throw new ArgumentNullException( "objectType" );
			if( objectType.IsAbstract
			   || objectType.IsInterface )
				throw new ApplicationException( "Cannot create an instance of type " + objectType.Name + ".  This type is either abstract or an interface" );
			
			object o = null;
			try{
				o = Activator.CreateInstance( objectType );
			}
			catch(Exception e){
				throw new ApplicationException("Could not activate an instance of " +objectType.Name, e );
			}
			
			if( !token.HasValues )
				return o;
			
			PropertyInfo[] pi = objectType.GetProperties();
			
			foreach( PropertyInfo p in pi )
			{
				o = AssignValueToProperty(o, p, token);
			}
			return o;
		}
		
		private object AssignValueToProperty(object o, PropertyInfo p, JToken token)
		{
			if( o == null )
				throw new ArgumentNullException( "o" );
			if( p == null )
				throw new ArgumentNullException( "p" );
			if( token == null )
				throw new ArgumentNullException( "token" );
			
			object propertyValue = null;
			string propertyName = string.Empty;
			
			//FIXME should not be working with virtual properties,
			//need to ensure we are working with properties
			//in the derived class not the base abstract class
			
			propertyName = p.Name;
			object[] attrs = p.GetCustomAttributes( typeof( JsonPropertyAttribute ), true );
			if( attrs != null
			   && attrs.Length == 1 )
				propertyName = ((JsonPropertyAttribute)attrs[0]).PropertyName;
			
			try{
				propertyValue = token.Value<object>( propertyName );
				
				p.SetValue(o, propertyValue , null );
			}
			catch{}
			return o;
		}
	}
}
